﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Text;
using System.Timers;

namespace Collector.Modbus
{
    class Modbus
    {
        private static SerialPort sp1 = null;
        private Timer timeOutTimer;
        private ushort recvBytesNum;
        private int delayTime;

        public int ConnectFailCount { get; private set; }
        public int TimeOutFailMaxTime { get; private set; }
        public object Recvlock { get; private set; }
        public Queue<byte[]> DataProcessorQueue { get; private set; }

        #region 加载串口配置   

        public bool LoadSerialConfig(string com, string BAUDRATE, string DATABITS, string STOP, string PARITY)
        {
            if (!sp1.IsOpen)   //没打开
            {
                try
                {
                    //设置串口号
                    string serialName = com;
                    sp1.PortName = serialName;

                    //设置各“串口设置”
                    string strBaudRate = BAUDRATE;
                    string strDateBits = DATABITS;
                    string strStopBits = STOP;
                    Int32 iBaudRate = Convert.ToInt32(strBaudRate);
                    Int32 iDateBits = Convert.ToInt32(strDateBits);

                    sp1.BaudRate = iBaudRate;       //波特率
                    sp1.DataBits = iDateBits;       //数据位
                    switch (STOP)            //停止位
                    {
                        case "1":
                            sp1.StopBits = StopBits.One;
                            break;
                        case "1.5":
                            sp1.StopBits = StopBits.OnePointFive;
                            break;
                        case "2":
                            sp1.StopBits = StopBits.Two;
                            break;
                        default:
                            //MessageBox.Show("Error：参数不正确!", "Error");
                            break;
                    }

                    switch (PARITY)             //校验位
                    {
                        case "NONE":
                            sp1.Parity = Parity.None;
                            break;
                        case "奇校验":
                            sp1.Parity = Parity.Odd;
                            break;
                        case "偶校验":
                            sp1.Parity = Parity.Even;
                            break;
                        default:
                            //MessageBox.Show("Error：参数不正确!", "Error");
                            break;
                    }

                    //如果打开状态，则先关闭一下
                    if (sp1.IsOpen == true)
                    {
                        sp1.Close();
                    }
                    sp1.Open();     //打开串口
                    return true;
                }
                catch (System.Exception ex)
                {
                    //SetSerialOpenFlag(false);
                    //Form1.ShowThrow(ex);
                    return false;
                }
            }
            else   //已经打开
            {
                return true;
            }
        }
        #endregion

        public void StartTimeOutTimer(UInt16 SendDataShowTimer, bool autoFlag)
        {
            //实例化Timer类，设置间隔时间为10000毫秒；
            timeOutTimer = new System.Timers.Timer(SendDataShowTimer);
            timeOutTimer.Elapsed += new System.Timers.ElapsedEventHandler(EndTimeProcess);
            timeOutTimer.AutoReset = autoFlag;//设置是执行一次（false）还是一直执行(rtue)；
            timeOutTimer.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件；
        }


        private void EndTimeProcess(Object sender, EventArgs e)
        {
            if (GetSerialOpenFlag())
            {
                recvBytesNum = (UInt16)sp1.BytesToRead;

                if (recvBytesNum == 0 && delayTime <= TimeOutFailMaxTime)
                {
                    delayTime++;
                    timeOutTimer.Start();    //定时器应该执行一次，然后在这从新开始，比如100毫秒后还未接收到数据，就记下数后重新开始定时器
                }
                else    //通过sp1.BytesToRead已经知道串口接收缓存区的大小，使用read函数直接取数，
                {
                    if (sp1.BytesToRead > 0)    //有数据，下面接收数据并校验数据
                    {
                        //接收16进制
                        try
                        {
                            lock (Recvlock)    //加锁
                            {
                                Byte[] receiveddata = new Byte[sp1.BytesToRead];        //创接建收字节数组
                                sp1.Read(receiveddata, 0, receiveddata.Length);         //读取数据
                                sp1.DiscardInBuffer();                                  //清空SerialPort控件的Buffer
                                if (receiveddata.Length <= 0)
                                    return;
                                DataProcessorQueue.Enqueue(receiveddata);
                            }
                            delayTime = 0;
                            recvBytesNum = 0;
                        }
                        catch (Exception ex)
                        {
                            //Form1.ShowThrow(ex);
                            return;
                        }
                    }
                    else        //超过多次定时都为串口缓冲区的数据都为空，则说明通讯超时
                    {
                        ConnectFailCount += 1;
                    }
                }
            }

        }

        private bool GetSerialOpenFlag()
        {
            throw new NotImplementedException();
        }


        #region  串口数据接收
        void sp1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (GetSerialOpenFlag()) //此处可能没有必要判断是否打开串口，但为了严谨性，我还是加上了
            {
                timeOutTimer.Stop();
                timeOutTimer.Start();
            }
            else
            {
                //Form1.ShowThrow("串口没有成功打开");
                return;
            }
        }
        #endregion

        #region  CRC16
        public static byte[] CRC16(byte[] data)
        {
            int len = data.Length;
            if (len > 0)
            {
                ushort crc = 0xFFFF;

                for (int i = 0; i < len; i++)
                {
                    crc = (ushort)(crc ^ (data[i]));
                    for (int j = 0; j < 8; j++)
                    {
                        crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
                    }
                }
                byte hi = (byte)((crc & 0xFF00) >> 8);  //高位置
                byte lo = (byte)(crc & 0x00FF);         //低位置

                return new byte[] { hi, lo };
            }
            return new byte[] { 0, 0 };
        }
        #endregion
    }
}
